/*
整合chrdev_regio(dev_t)n和cdev

多个字符设备(主设备号相同，次设备号不同)

那么多个字符设备又如何区分呢？
代码都是调用这个模块的，就是多个设备要用一份代码，只能通过回调函数的参数来判别。
struct file有个变量private_data，一般在内核模块open完成设置，read\write等即可区分

安装次模块，将
    在/proc/devices文件里生成设备(chrdev)及其主设备号
    可用使用mknod创建三个 可打开的 字符设备，设备号为:255:0;255:1;255:2
        若再多创建，打开时报错cat /dev/lxz-device3: /dev/lxz-device3: No such device or address

    可打开关闭设备文件，打印其存储的私有数据
*/

#include "linux/init.h"
#include "linux/module.h"
#include "linux/cdev.h"
#include "linux/string.h"

static dev_t chrdev_id = 0;
static uint32_t chrdev_id_major = 255;
static uint32_t chrdev_id_minimor = 0;
static const uint32_t count = 3;//三个字符设备
static const char *chrdev_region_name = "multi-chrdev";
static uint8_t registered = 0;
static struct cdev chrdev;//字符设备内核实例
static uint32_t values[] = {11, 22, 33};

/*
!!!驱动注册的函数，所以函数传来的参数都来自内核机制!!!
*/
int chrdev_open(struct inode *node, struct file *f)
{
    printk("file=%s,fun=%s,line=%d\n", __FILE__, __FUNCTION__, __LINE__);

    // 打印设备号，主设备号，次设备号
    printk("chrdev_open,node->i_rdev=%u,MAJOR=%u,MINOR=%u\n", node->i_rdev, MAJOR(node->i_rdev), MINOR(node->i_rdev));

    // 这里知道是0,1,2
    f->private_data = (void *)(values + MINOR(node->i_rdev));
    printk("set private_data to %u\n", values[MINOR(node->i_rdev)]);

    return 0;
}

int chrdev_release(struct inode *node, struct file *f)
{
    printk("file=%s,fun=%s,line=%d\n", __FILE__, __FUNCTION__, __LINE__);

    // 打印设备号，主设备号，次设备号
    printk("chrdev_release,node->i_rdev=%u,MAJOR=%u,MINOR=%u\n", node->i_rdev, MAJOR(node->i_rdev), MINOR(node->i_rdev));

    printk("f->private_data=%u\n", *((unsigned int *)(f->private_data)));

    return 0;
}

static struct file_operations chrdev_ops = 
{
    .owner = THIS_MODULE,
    .open = chrdev_open,
    .release = chrdev_release,
};

static int __init chrdev_init(void)
{
    int ret = -1;

    printk("file=%s,fun=%s,line=%d\n", __FILE__, __FUNCTION__, __LINE__);

    // 手动生成设备号
    chrdev_id = MKDEV(chrdev_id_major, chrdev_id_minimor);
    printk("chrdev_id[MAJOR=%u,MINOR=%u]\n", MAJOR(chrdev_id), MINOR(chrdev_id));

    // 注册设备号
    ret = register_chrdev_region(chrdev_id, count, chrdev_region_name);
    if (ret < 0)
    {
        printk("register_chrdev_region error\n");
        return -1;
    }
    printk("register_chrdev_region succeed\n");
    printk("chrdev_id[MAJOR=%u,MINOR=%u]\n", MAJOR(chrdev_id), MINOR(chrdev_id));

    // 初始化字符设备
    cdev_init(&chrdev, &chrdev_ops);

    // 添加多个字符设备
    ret = cdev_add(&chrdev, chrdev_id, count);
    if (ret < 0)
    {
        printk("cdev_add error\n");

        unregister_chrdev_region(chrdev_id, count);
        return -1;
    }
    printk("cdev_add succeed,chrdev_id=%u,count=%u\n", chrdev_id, count);

    registered = 1;

    return 0;
}

static void __exit chrdev_exit(void)
{
    printk("file=%s,fun=%s,line=%d\n", __FILE__, __FUNCTION__, __LINE__);

    if (registered)
    {
        // 删除字符设备
        cdev_del(&chrdev);

        // 注销字符设备号
        unregister_chrdev_region(chrdev_id, count);

        printk("unregister_chrdev_region succeed\n");
    }
}

module_init(chrdev_init);
module_exit(chrdev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lxz");
